using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using System;

using SMGCore.EventSys;
using Game.Networking.Events;
using Voxels.Utils;
using Game.Networking.Utils;

using MemoryPack;
using LiteDB;
using UnityEngine;

namespace Game.Networking.Server {
	public class ServerSaveLoadController : ServerSideController<ServerSaveLoadController> {
		public ServerSaveLoadController(ServerGameManager owner) : base(owner) { }

		const string SAVE_DATA_FILE_NAME = "saveload.info";

		string SaveDirPath => GameManager.PersistentDataPath + "/Save/";

		bool   _ioAlive  = false;
		Thread _ioThread = null;

		//ConcurrentQueue<KeyValuePair<Vector3Int, ChunkData>> _chunkSaveQueue = new ConcurrentQueue<KeyValuePair<Vector3Int, ChunkData>>();
		//ConcurrentQueue<Vector3Int>                          _chunkLoadQueue = new ConcurrentQueue<Vector3Int>();
		MainDataHolder                                 _saveData       = new MainDataHolder();

		LiteDatabase _db = null;

		public string SavePath {
			get {
				return string.Format("{0}{1}/", SaveDirPath, WorldOptions.WorldName);
			}
		}

		public string WorldSavePath {
			get {
				return string.Format("{0}{1}/World/", SaveDirPath, WorldOptions.WorldName);
			}
		}

		public override void Init() {
			base.Init();

			CustomTypeMappers.InitMappers();

			_ioAlive = true;
			_ioThread = new Thread(IOThread) {
				Name = "Server IO",
				IsBackground = true
			};
			_ioThread.Start();

			var path = SavePath;
			if ( !Directory.Exists(path) ) {
				Directory.CreateDirectory(path);
			}
			if ( !Directory.Exists(WorldSavePath) ) {
				Directory.CreateDirectory(WorldSavePath);
			}
		}

		public override void Load() {
			base.Load();
			var loadedHolder = LoadSaveFile<MainDataHolder>(SAVE_DATA_FILE_NAME);
			if ( loadedHolder != null ) {
				_saveData = loadedHolder;
				ServerGameManager.Time = _saveData.GameTime;
			}
			_db = new LiteDatabase(@SavePath + "main.db");
		}

		public override void Save() {
			base.Save();
			_saveData.GameTime = ServerGameManager.Time;
			SaveFileToDisk(SAVE_DATA_FILE_NAME, _saveData);
			_db?.Commit();
		}

		public override void Reset() {
			base.Reset();
			_ioAlive = false;

			if ( _db != null ) {
				_db.Dispose();
				_db = null;
			}
			Save();
		}

		public ILiteCollection<ClientProfile> GetClientsDatabase() {
			var coll = _db.GetCollection<ClientProfile>("clients");
			coll.EnsureIndex(x => x.Name, true);
			return coll;
		}

		public ILiteCollection<BanInfo> GetBansDatabase() {
			var coll = _db.GetCollection<BanInfo>("bans");
			coll.EnsureIndex(x => x.Name, false);
			coll.EnsureIndex(x => x.IP, false);
			return coll;
		}

		public ILiteCollection<ChatMessage> GetChatDatabase() {
			var coll = _db.GetCollection<ChatMessage>("chat");
			coll.EnsureIndex(x => x.PlayerName);
			coll.EnsureIndex(x => x.Time);
			return coll;
		}

		public PlayerEntitySaveInfo GetPlayerInfo(string name) {
			var coll = _db.GetCollection<PlayerEntitySaveInfo>("players");
			var result = coll.FindOne(x => x.Name == name);
			return result;
		}

		public void UpdatePlayerInfo(PlayerEntitySaveInfo info) {
			if ( info == null || string.IsNullOrEmpty(info.Name) ) {
				throw new ArgumentNullException("Player info is null or has invalid name");
			}
			var coll = _db.GetCollection<PlayerEntitySaveInfo>("players");
			
			if ( !coll.Update(info) ) {
				coll.Insert(info);

			}
			coll.EnsureIndex(x => x.Name);
		}


		public T LoadSaveFile<T>(string name) where T: class {
			return LoadFile<T>(SavePath + name);
		}

		public T LoadOrCreateSaveFile<T>(string name) where T : class, new() {
			var loaded = LoadSaveFile<T>(name);
			if ( loaded != null ) {
				return loaded;
			}
			var created = new T();
			return created;
		}

			public void SaveFileToDisk<T>(string name, T file) where T : class {
			SaveFile<T>(SavePath + name, file);
		}

		void IOThread() {
			while (_ioAlive) {
				Thread.Sleep(10);
				if ( _ioAlive ) {
					
				}				
			}
		}

		T LoadFile<T>(string filePath) where T : class {
			if ( !File.Exists(filePath) ) {
				return null;
			}
			using ( var file = File.OpenRead(filePath) ) {
				if (file.Length == 0 ) {
					return null;
				}
				using ( var reader = new BinaryReader(file) ) {
					var length = reader.ReadInt32();
					var obj = MemoryPackSerializer.Deserialize<T>(reader.ReadBytes(length));
					return obj;
				}
			}
		}

		void SaveFile<T>(string filePath, T obj) where T : class {
			using ( var file = File.Create(filePath) ) {
				using ( var chunkWriter = new BinaryWriter(file) ) {
					var data = MemoryPackSerializer.Serialize(obj);
					chunkWriter.Write(data.Length);
					chunkWriter.Write(data);
				}
			}
		}
	}
}
